package org.cloud.admin.auth.controller;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.HttpStatus;
import org.cloud.admin.auth.constant.AuthConstants;
import org.cloud.admin.auth.constant.AuthRedisConstants;
import org.cloud.admin.auth.holder.AppHolder;
import org.cloud.admin.auth.service.AuthService;
import org.cloud.admin.auth.service.SysMenuService;
import org.cloud.admin.auth.service.SysUserService;
import org.cloud.admin.auth.vo.LoginedUserVo;
import org.cloud.admin.auth.vo.SysLoginVo;
import org.cloud.common.model.R;
import org.cloud.common.util.BeanUtils;
import org.cloud.common.util.CollectionUtils;
import org.cloud.common.util.DigestUtils;
import org.cloud.common.util.StreamUtils;
import org.cloud.common.util.StringUtils;
import org.cloud.common.util.UUIDUtils;
import org.cloud.module.common.entity.SysMenuEntity;
import org.cloud.module.common.entity.SysUserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.google.code.kaptcha.Producer;

/**
 * 鉴权服务
 * @author wangyuan
 * @since 2019年6月5日 下午5:58:04
 *
 */
@RestController
public class SysAuthController {
    
    /**
     * 验证码过期时间，默认5分钟
     */
    private static final long CAPTCHA_TIMEOUT = 5L;
	
	@Autowired
	private SysUserService sysUserService;
	
	@Autowired
	private SysMenuService sysMenuService;
	
	@Autowired
	private AuthService authService;
	
	@Autowired
    private Producer producer;
	
	@Resource(name = "valueOperations")
	private ValueOperations<String, String> valueOperations;
	
	@Resource(name = "valueOperations")
    private ValueOperations<String, LoginedUserVo> loginedValueOperations;
	
	/**
	 * 验证码
	 */
	@GetMapping("captcha.jpg")
	public void captcha(HttpServletResponse response, String uuid) throws IOException {
		response.setHeader("Cache-Control", "no-store, no-cache");
		response.setContentType("image/jpeg");
		
		//生成文字验证码
        String code = producer.createText();
        
        //保存至redis缓存中
        valueOperations.set(AuthRedisConstants.CAPTCHA_KEY_PREFIX + uuid, code, CAPTCHA_TIMEOUT, TimeUnit.MINUTES);

		// 获取图片验证码
		BufferedImage image = producer.createImage(code);

		//写入输出流
		ServletOutputStream out = response.getOutputStream();
		ImageIO.write(image, "jpg", out);
		StreamUtils.close(out);
	}
	
	/**
	 * 登录
	 */
	@PostMapping("/sys/login")
	public R login(@ModelAttribute SysLoginVo loginVo) throws Exception {
		String captchaCode = valueOperations.get(AuthRedisConstants.CAPTCHA_KEY_PREFIX + loginVo.getUuid());
		if(StringUtils.isEmpty(captchaCode)) {
		    return R.error("验证码不正确");
		}
		if(!captchaCode.equalsIgnoreCase(loginVo.getCaptcha())){
		    return R.error("验证码不正确");
        }
		
		//删除
		valueOperations.getOperations().delete(AuthRedisConstants.CAPTCHA_KEY_PREFIX + loginVo.getUuid());

		// 用户信息
		SysUserEntity user = sysUserService.queryByUserName(loginVo.getUsername());
		
		// 账号不存在、密码错误
		if (user == null || !user.getPassword().equals(DigestUtils.sha256Hex(loginVo.getPassword(), user.getSalt()))) {
			return R.error("账号或密码不正确");
		}

		// 账号锁定
		if (user.getStatus() == 0) {
			return R.error("账号已被锁定,请联系管理员");
		}
		
		LoginedUserVo loginedUser = new LoginedUserVo();
		BeanUtils.copyProperties(user, loginedUser);
        
		//生成token
        String token = UUIDUtils.generateToken();
		
        loginedValueOperations.set(AuthRedisConstants.LOGINED_USER_KEY_PREFIX + token, 
                loginedUser, AuthConstants.LOGINED_USER_TIMEOUT, TimeUnit.MINUTES);
		
		return R.ok().put("token", token).put("expire", AuthConstants.LOGINED_USER_TIMEOUT);
	}
	
	/**
	 * 退出
	 */
	@PostMapping("/sys/logout")
	public R logout(String token) {
	    loginedValueOperations.getOperations().delete(AuthRedisConstants.LOGINED_USER_KEY_PREFIX + token);
		return R.ok();
	}
	
	/**
	 * token验证，服务鉴权
	 * @param accessToken
	 * @param requestUri
	 * @return
	 */
	@RequestMapping(value = "/auth/service", method = RequestMethod.POST)
	public R authenticateService(@RequestParam("accessToken")String accessToken, 
			@RequestParam("requestUri")String requestUri) {
		//1. Token鉴权
		if(StringUtils.isBlank(accessToken)){
			return R.error(HttpStatus.SC_UNAUTHORIZED, "未经授权的请求");
        }
		
		//根据accessToken，查询用户信息
		LoginedUserVo loginedUser = loginedValueOperations.get(AuthRedisConstants.LOGINED_USER_KEY_PREFIX + accessToken);
        //token失效
        if(loginedUser == null){
        	return R.error(HttpStatus.SC_UNAUTHORIZED, "token失效，请重新登录");
        }
        
        //2. 服务鉴权（是否有访问这个服务的权限）
        SysMenuEntity menu = sysMenuService.getByUri(requestUri);
        if(menu == null || StringUtils.isEmpty(menu.getMenuPerms())) {
        	return R.ok("此服务无权限，可以访问该服务");
        }
        
        //查询用户具有的权限
        Set<String> permsOfUser = authService.getUserPermissions(AppHolder.getLoginedUserId());
        if(CollectionUtils.isEmpty(permsOfUser)) {
        	return R.error(HttpStatus.SC_UNAUTHORIZED, "用户无权限访问该服务");
        }
        
        String[] perms = menu.getMenuPerms().split(",");
        for(String permItem : perms) {
        	if(StringUtils.isBlank(permItem)) {
        		continue;
        	}
        	
        	//只要具有一个权限就可以访问（或的关系）
        	if(permsOfUser.contains(permItem)) {
        		return R.ok("鉴权成功，可以访问该服务");
        	}
        }
		
		return R.error(HttpStatus.SC_UNAUTHORIZED, "鉴权结束，用户无访问权限");
	}
}
